from pml import PML
from pml import Objects
from pml.Bytecode import constants
from pml.Parser.lexer import constants as TokenConstants

__all__ = 'Bytecode', 'BytecodeBuilder'

class Bytecode(Objects.object.typeobject):
    def __init__(self, operator=None, value=None, index=0, bp=None, ep=None):
        super().__init__('bytecode')
        self.operator = operator
        self.value = value
        self.index = index
        self.bp = bp
        self.ep = ep
    def __repr__(self):
        return f'Bytecode(op={self.operator}{"" if self.value is None else " => "}{"" if self.value is None else self.value}\t\t\t\t\t\t\tindex={self.index})'

    def __reduce__(self):
        return Bytecode, (self.operator, self.value, self.index, self.bp, self.ep)

    def getObject(self):
        self.methods['__init__'] = self.tp_init

        return self

    @staticmethod
    def tp_init(frame, this):
        pass

def buildTheBytecodesOfExpression(ast, index, builder: 'BytecodeBuilder', get=True):
    bytecodes = []

    if ast.name == 'expression':
        return buildTheBytecodesOfExpression(ast.expression.obj, index, builder)

    if ast.name == 'number':
        bytecodes.append(Objects.object.PMTypeObject(Bytecode(constants.LOAD, (ast.type, ast.value), index, ast.bp, ast.ep)))
    elif ast.name in ('name', 'member'):
        builder.buildTheBytecodeOfName(ast, get)

    elif ast.name == 'unary':
        b = buildTheBytecodesOfExpression(ast.right.obj, index, builder)
        [bytecodes.append(i)for i in b]
        bytecodes.append(Objects.object.PMTypeObject(Bytecode(constants.LEFT_UNARY_OPERATOR if ast.left else constants.RIGHT_UNARY_OPERATOR, ast.op, index + len(b), ast.bp, ast.ep)))
    elif ast.name == 'binary':
        if ast.op == TokenConstants.TV_SET_VALUE:
            temp = builder.index
            builder.index = index
            builder.buildTheBytecodeOfName(ast.left)
            builder.index = temp + 1
            index += 1
            l = []
        else:
            l = buildTheBytecodesOfExpression(ast.left.obj, index, builder)
        [bytecodes.append(i) for i in l]
        index += len(l) + 1
        r = buildTheBytecodesOfExpression(ast.right.obj, index + 1, builder)
        [bytecodes.append(i) for i in r]
        bytecodes.append(Objects.object.PMTypeObject(Bytecode(constants.BINARY_OPERATOR, ast.op, index + len(r) + 1, ast.bp, ast.ep)))

    elif ast.name == 'call':
        builder.index = index
        builder.buildTheBytecodeOfName(ast.id, get=True)
        size = 0
        index = builder.index
        for arg in ast.args[::-1]:
            size += 1
            [bytecodes.append(bytecode) for bytecode in buildTheBytecodesOfExpression(arg.obj, index, builder)]
            index += len(bytecodes)
        bytecodes.append(Objects.object.PMTypeObject(Bytecode(constants.LOAD_CONST, size, index, ast.bp, ast.ep)))
        bytecodes.append(Objects.object.PMTypeObject(Bytecode(constants.CALL, None, index + 1, ast.bp, ast.ep)))

    return bytecodes

class BytecodeBuilder(Objects.object.typeobject):
    def __init__(self, ast=None, index=1):
        super().__init__('Bytecode')
        self.ast = ast
        self.bytecodes = []
        self.index = index

    def getObject(self):
        self.methods['__init__'] = self.tp_init

        self.methods['buildBytecode'] = self.buildBytecode

        return self

    def buildTheBytecodeOfName(self, ast, get=False):
        if isinstance(ast, str):
            self.load_id(ast, False, None, None)
        else:
            ast = ast.obj if isinstance(ast, Objects.object.PMTypeObject) else ast
            if ast.name == 'name':
                self.load_id(ast.id, False, ast.bp, ast.ep)
                if get:
                    self.load_name(False, ast.bp, ast.ep)
            elif ast.name == 'number':
                self.load((ast.type, ast.value), ast.bp, ast.ep)
            else:
                self.buildTheBytecodeOfName(ast.father, get)
                self.load_id(ast.member, not get, ast.bp, ast.ep)
                if get:
                    self.load_name(get, ast.bp, ast.ep)

    @staticmethod
    def buildBytecode(frame, this: 'BytecodeBuilder'):
        for ast in this.ast.obj.body:
            ast = ast.obj

            if ast.name == 'expression':
                bytecodes = buildTheBytecodesOfExpression(ast, this.index, this)
                [this.bytecodes.append(bytecode) for bytecode in bytecodes]
                this.index += len(bytecodes)

            elif ast.name in ('variable', 'let', 'const'):
                this.buildTheBytecodeOfVariable(ast)

            elif ast.name[:3] == 'for':
                this.buildTheBytecodeOfFor(ast)

            elif ast.name == 'if':
                this.buildTheBytecodeOfIf(ast)

            elif ast.name == 'function-common':
                this.buildTheBytecodeOfFunction(ast)

            elif ast.name == 'import' or ast.name == 'include':
                this.buildTheBytecodeOfModule(ast)

        this.add(constants.EXIT, None, None, None)
        return PML.object.ChangeObject.PMLList_FromList(this.bytecodes)

    def buildTheBytecodeOfVariable(self, ast):
        bytecodes = buildTheBytecodesOfExpression(ast.body.obj, self.index, self)
        [self.bytecodes.append(bytecode) for bytecode in bytecodes]
        self.index += len(bytecodes)
        self.buildTheBytecodeOfName(ast.id)
        self.store_name(ast.name, ast.id.obj.bp, ast.id.obj.ep)

    def buildTheBytecodeOfFor(self, ast):
        if ast.name == 'for-in':
            self.buildBlock(ast.body, None)
            bytecodes = buildTheBytecodesOfExpression(ast.iter.obj, self.index, self)
            [self.bytecodes.append(bytecode) for bytecode in bytecodes]
            self.index += len(bytecodes)
            self.buildTheBytecodeOfName(ast.loopVariable)
            self.add(constants.FOR, None, ast.bp, ast.ep)

    def buildTheBytecodeOfIf(self, ast):
        if ast.body.obj.body.__len__() == 0 and (True if ast.elseBody is None else ast.elseBody.obj.body.obj.body.__len__() == 0):
            return
        if True if ast.elseBody is None else ast.elseBody.obj.body.__len__() == 0:
            bytecodes = buildTheBytecodesOfExpression(ast.expression.obj.expression.obj, self.index, self)
            [self.bytecodes.append(bytecode) for bytecode in bytecodes]
            self.index += len(bytecodes)
            del bytecodes
            self.if_false_jump(0, None, None)
            temp = self.bytecodes[-1]
            self.buildBlock(ast.body, None)
            self.run_block(None, None)
            temp.obj.value = self.index
        else:
            bytecodes = buildTheBytecodesOfExpression(ast.expression.obj.expression.obj, self.index, self)
            [self.bytecodes.append(bytecode) for bytecode in bytecodes]
            self.index += len(bytecodes)
            del bytecodes
            self.if_false_jump(0, None, None)
            temp = self.bytecodes[-1]
            self.buildBlock(ast.body, None)
            self.run_block(None, None)

            self.jump(0, None, None)
            temp.obj.value = self.index
            temp = self.bytecodes[-1]
            self.buildBlock(ast.elseBody, None)
            self.run_block(None, None)
            temp.obj.value = self.index

    def buildTheBytecodeOfFunction(self, ast):
        self.buildBlock(ast.body, ast.id)
        len = 0
        for arg in ast.args:
            len += 1
            self.load_id(arg, False, arg.obj.bp, arg.obj.ep)
        self.load_const(len, None, None)
        self.load_id(ast.id, False, ast.id.obj.bp, ast.id.obj.ep)
        self.add(constants.FUNCTION, None, ast.bp, ast.ep)

    def buildTheBytecodeOfModule(self, ast):
        self.buildTheBytecodeOfName(ast.id.obj.expression)
        if ast.name == 'import':
            self.add(constants.IMPORT, False, ast.bp, ast.ep)
        else:
            self.add(constants.IMPORT, True, ast.bp, ast.ep)


    def buildBlock(self, ast, name):
        self.begin_block(None, None)
        bytecodeBuilderOfBody = BytecodeBuilder(ast)
        [self.bytecodes.append(bytecode)for bytecode in bytecodeBuilderOfBody.buildBytecode(None, bytecodeBuilderOfBody)]
        self.index += bytecodeBuilderOfBody.index - 1
        self.end_block(None, None)
        self.load_id(name, False, None, None)
        self.build_block(None, None)

    def add(self, operator, value, bp, ep):
        self.bytecodes.append(Objects.object.PMTypeObject(Bytecode(operator, value, self.index, bp, ep)))
        self.index += 1

    def load(self, value, bp, ep):
        self.add(constants.LOAD, value, bp, ep)

    def load_frame(self, bp, ep):
        self.add(constants.LOAD_FRAME, None, bp, ep)

    def load_const(self, value, bp, ep):
        self.add(constants.LOAD_CONST, value, bp, ep)

    def load_id(self, id,  memeber, bp, ep):
        self.add(constants.LOAD_ID, (memeber, id if isinstance(id, str) else (None if id is None else id.obj.id)), bp, ep)

    def load_name(self, member, bp, ep):
        self.add(constants.LOAD_NAME, member, bp, ep)

    def store_name(self, type, bp, ep):
        self.add(constants.STORE_NAME, type, bp, ep)

    def begin_block(self, bp, ep):
        self.add(constants.BEGIN_BLOCK, None, bp, ep)

    def end_block(self, bp, ep):
        self.add(constants.END_BLOCK, None, bp, ep)

    def build_block(self, bp, ep):
        self.add(constants.BUILD_BLOCK, None, bp, ep)

    def run_block(self, bp, ep):
        self.add(constants.RUN_BLOCK, None, bp, ep)

    def if_true_jump(self, index, bp, ep):
        self.add(constants.IF_TRUE_JUMP, index, bp, ep)

    def if_false_jump(self, index, bp, ep):
        self.add(constants.IF_FALSE_JUMP, index, bp, ep)

    def jump(self, index, bp, ep):
        self.add(constants.JUMP, index, bp, ep)


    @staticmethod
    def tp_init(frame, this):
        pass
